/*
 * @Description: 上传。如果文件大小大于分片大小则分片上传。否则直接上传
 */
import axios from 'axios';
import globalMethods from '@/plugins/methods';

const CancelToken = axios.CancelToken;
//默认分片大小（kb）10M
const DEFAULT_CHUNK_SIZE = 10 * 1024 * 1024;
export default class UploadFile {
    //分片上传进度
    chunkProcess = 0;
    //是否可以上传
    isCanUpload = true;
    //用于保存取消当前项请求的方法
    cancelFunction = null;
    /**
     * @description: options 传入的配置，参数如下↓
     * @param {Function} finish(result) 上传成功结束时执行。返回接口返回值
     * @param {Function} process(percentage) 上传过程中执行。返回当前上传进度
     * @param {Function} fail 上传失败时执行
     * @param {String} breakpointId 同一时刻的随机字符串。拿来断点续传用
     * @param {Number}chunkSize 分片大小（kb）默认10M
     * @param {Object}上传相关方法。必传。值为axios等请求的方法，返回请求结果的promise对象
     * request:{
     *  upload:上传
     *  getState:获取状态
     * }
     * @param {Object}fileUploadParams 文件接口的参数。列表如下：
     *  maxSize 文件大小限制 kb
     *  extensions 允许的文件扩展名
     *  isEncrypt 是否加密
     *  type 文件类型
     *  firstSave 是否入库
     */
    constructor({
        process,
        finish,
        fail,
        breakpointId,
        chunkSize = DEFAULT_CHUNK_SIZE,
        request: { upload, getState },
        fileUploadParams,
    }) {
        this.process = process;
        this.finish = finish;
        this.fail = fail;
        this.breakpointId = breakpointId;
        this.chunkSize = chunkSize;
        this.uploadRequest = upload;
        this.getStateRequest = getState;
        this.fileUploadParams = fileUploadParams;
    }
    /**
     * @description: 开始上传
     * @param {File}file 上传的对象
     * @return {*}
     */
    uploadStart(file) {
        //设置上传文件，以便返回reUpload的时候重新上传
        this.file = file;
        console.log('uploadStart', file);
        //如果已经上传完毕的直接调接口获取数据
        //接口貌似不支持。只得手动上传1b的数据，看起来像是秒传
        if (file.size === file.startIndex) {
            file.startIndex--;
        }
        //判断大小是否超过分片大小
        if (file.size > this.chunkSize) {
            //超过就启用分片上传
            this.chunkUploadStart(file);
        } else {
            //没超过就直接上传
            this.uploadFile(file);
        }
    }
    /**
     * @description: 终止上传
     * @param {*}
     * @return {*}
     */
    stopUpload() {
        this.isCanUpload = false;
        this.cancelFunction && this.cancelFunction('cancel');
    }
    /**
     * @description: 终止并重新上传
     * @return {*}
     */
    reUpload() {
        this.stopUpload();
        //重新上传直接重头开始
        this.file.startIndex = 0;
        this.uploadStart(this.file);
    }
    /**
     * @description: 上传文件
     * @param {File}file 上传的文件对象。直接上传为file,分片上传为：{blob:file}
     * @param {Number}totalSize 文件总大小（分片上传用）
     * @return {*}
     */
    async uploadFile(file, totalSize) {
        console.log('uploadFile', file);
        const data = new FormData();
        //	文件二进制流（分片）或者file对象（直接上传）
        data.append('file', file.blob || file);
        //	文件大小,这是指文件总大小，而不是分片大小
        data.append('fileSize', totalSize || file.size);
        //同一时刻的随机字符串。拿来断点续传用
        data.append('currentPageId', this.breakpointId);
        //最近修改日志。就是文件的lastModified
        data.append('lastModifiedDate', globalMethods.getFileLastModifiedDate(file).toString());
        //文件上传位置标记(起始位置，不分片就是0)
        data.append('startIndex', file.startIndex || 0);
        //文件名
        data.append('fileName', file.name);
        //密级设置
        data.append('secretLevel', file.secretLevel);
        data.append('secretLevelName', file.secretLevelName);
        //如果有额外定制的参数,就加进去
        if (this.fileUploadParams) {
            Object.entries(this.fileUploadParams, ([key, value]) => {
                data.append(key, value);
            });
        }
        //调接口上传
        const res = await this.uploadRequest(data, {
            //取消请求，取消上传用
            cancelToken: new CancelToken((c) => {
                // An executor function receives a cancel function as a parameter
                this.cancelFunction = c;
            }),
            onUploadProgress: ({ loaded, total }) => {
                //分片上传进度
                if (totalSize) {
                    this.process(Math.floor(((this.chunkProcess + loaded) / totalSize) * 100));
                    //一片传完,就累加到进度去
                    if (loaded === total) {
                        this.chunkProcess += loaded;
                    }
                } else {
                    //普通上传
                    this.process(Math.floor((loaded / total) * 100));
                }
            },
            timeout: 0,
        }).catch(({ isCancel }) => {
            this.fail(isCancel);
            this.stopUpload();
        });
        console.log(res);
        //适配以前借口
        if (!res) return;
        //没成功，直接干掉
        if (res.status !== 200) return this.fail();
        //看以前的接口是这样逻辑,应该是指代重新上传，具体场景不明
        if (res.reUpload) {
            this.reUpload();
            return true;
        } else {
            //上传完毕
            if (res.end) {
                //适配一下创建日期字段
                res.att.createDate = res.att.createdate;
                this.finish(res.att);
                this.stopUpload();
            }
        }
    }
    /**
     * @description: 创建分片参数
     * @param {File}file 待分片的文件
     * @param {Number}startIndex 分片开始位置
     * @param {Number}endIndex 分片结束位置
     * @return {Array}分片上传参数数组
     */
    createChunkParamList(file, startIndex, endIndex) {
        return [
            {
                blob: file.slice(startIndex, endIndex),
                startIndex: startIndex,
                lastModified: globalMethods.getFileLastModifiedDate(file),
                name: file.name,
                secretLevel: file.secretLevel,
                secretLevelName: file.secretLevelName,
            },
            file.size, //传入文件总大小，以计算进度
        ];
    }
    /**
     * @description: 启用分片上传
     * @param {File}file 待分片的文件
     * @return {*}
     */
    async chunkUploadStart(file) {
        //有起始值即为断点续传
        let startIndex = file.startIndex || 0;
        //进度初始值为断点续传值
        this.chunkProcess = startIndex;
        //切片处理
        let surplus = file.size - startIndex;
        while (surplus > 0) {
            //结束位置
            let endIndex = startIndex + this.chunkSize;
            //如果剩下的不够一片了，就为一片
            if (surplus < this.chunkSize) {
                endIndex = startIndex + surplus;
                //退出切片
                surplus = 0;
            } else {
                //减少剩余量
                surplus -= this.chunkSize;
            }

            //不能上传就终止上传。上传切片。如果需要重传，就终止上传
            if (
                !this.isCanUpload ||
                (await this.uploadFile(...this.createChunkParamList(file, startIndex, endIndex)))
            ) {
                break;
            }
            startIndex = endIndex;
        }
    }
}
